﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Algorithm_Comparison
{
    /// <summary>
    /// Iterated Local Search based on PhiP criterion
    /// Keeps track of the best recorded maximin LHD
    /// MaxNoImprove is set to 1000
    /// </summary>
    class IteratedLocalSearchPhiP
    {
        public static readonly int MaxNoImprove = 1000;

        public readonly LHD InitialLHD;
        public int PValue;
        public readonly long MaxNrOfExchanges;
        public readonly bool Euclidian;


        public int NrNoImprovements;
        public long NrOfExchanges;

        public LHD BestRecordedMaximinLHD;
        public LHD EndResult;

        public IteratedLocalSearchPhiP(LHD initialLHD, int pValue, long maxExchanges, bool euclidian = true)
        {
            InitialLHD = initialLHD;
            PValue = pValue;
            MaxNrOfExchanges = maxExchanges;
            BestRecordedMaximinLHD = InitialLHD;
            NrNoImprovements = 0;
            NrOfExchanges = 0;
            Euclidian = euclidian;

            InitialLHD.CalculateDistanceMatrix(Euclidian);
            InitialLHD.CalculatePointMinDistance();
            InitialLHD.CalculatePhiP(PValue);
            Run();
        }

        /// <summary>
        /// Variable names correspond to those used in paper
        /// </summary>
        private void Run()
        {
            LHD X0 = InitialLHD;
            LHD Xstar = LocalSearch(X0); // x*
            do
            {
                LHD Xaccent = Perturbate(Xstar); // x'
                Xaccent.CalculatePhiP(PValue);
                LHD Xstaraccent = LocalSearch(Xaccent); // x*'
                if (Xstaraccent.LastCalculatedPhiP < Xstar.LastCalculatedPhiP)
                    Xstar = Xstaraccent;
            } while (NrNoImprovements < MaxNoImprove && NrOfExchanges < MaxNrOfExchanges);
            EndResult = Xstar;
        }
        public LHD LocalSearch(LHD lhd)
        {
            LHD x = new LHD(lhd);
            x.CalculatePhiP(20);
            bool improvementFound, atleastOneImprovement = false;
            do
            {
                improvementFound = false;
                for (int i = 0; i < x.NumberOfPoints; i++)
                    for (int j = 0; j < x.NumberOfPoints; j++)
                        if (i != j) // exchanges between non critical points allowed
                            for (int l = 0; l < x.Dimension; l++)
                            {
                                NrOfExchanges++;
                                LHD y = x.LocalMove(i, j, l, Euclidian); //LocalMove recalculates distance matrix and pointmindistances
                                y.CalculatePhiP(PValue);
                                
                                if (y.LastCalculatedPhiP < x.LastCalculatedPhiP)
                                {
                                    if (y.MinDistance > BestRecordedMaximinLHD.MinDistance)
                                        BestRecordedMaximinLHD = y;
                                    improvementFound = true;
                                    atleastOneImprovement = true;
                                    x = y;
                                }
                            }
            } while (improvementFound);

            if (atleastOneImprovement)
                NrNoImprovements = 0;
            else
                NrNoImprovements++;
            return x;
        }

        static Random random = new Random();
        /// <summary>
        /// Performs a perturbation on the given latin hyper cube as described in the paper
        /// </summary>
        /// <param name="lhd"></param>
        /// <returns></returns>
        public LHD Perturbate(LHD lhd)
        {
            LHD result = new LHD(lhd);

            //step 1: choose i and j so i < j and j - i >= 2 0 1 2 3 4
            int i = random.Next(0, result.NumberOfPoints - 2);
            int j = random.Next(i + 2, result.NumberOfPoints);

            //step 2: randomly choose a component l
            int l = random.Next(0, result.Dimension);

            //cyclic order exchange
            result.CyclicOrderExchange(i, j, l);

            return result;
        }
    }
}
